package top.jiangqiang.qianyi.common.log.aop;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.date.TimeUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.http.meta.HeaderName;
import org.dromara.hutool.http.server.servlet.ServletUtil;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.ErrorResponseException;
import org.springframework.web.bind.annotation.RequestBody;
import top.jiangqiang.qianyi.common.auth.SessionHolder;
import top.jiangqiang.qianyi.common.base.model.response.Result;
import top.jiangqiang.qianyi.common.constants.http.*;
import top.jiangqiang.qianyi.common.constants.log.SysLogType;
import top.jiangqiang.qianyi.common.exception.BaseException;
import top.jiangqiang.qianyi.common.log.annotations.Log;
import top.jiangqiang.qianyi.common.log.model.SysLogSaveRequest;
import top.jiangqiang.qianyi.common.log.service.LogPersistenceService;
import top.jiangqiang.qianyi.common.properties.AuthProperties;
import top.jiangqiang.qianyi.common.utils.JsonUtil;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Aspect
@RequiredArgsConstructor
public class LogAop {

    private final HttpServletRequest request;
    private final AuthProperties authProperties;
    private final LogPersistenceService logPersistenceService;

    // 定义一个切入点，匹配Controller类中带有@Log注解的方法
    @Pointcut("""
            (@within(org.springframework.stereotype.Controller)
            || @within(org.springframework.web.bind.annotation.ControllerAdvice)
            || @within(org.springframework.web.bind.annotation.RestController)
            || @within(org.springframework.web.bind.annotation.RestControllerAdvice))
            && @annotation(top.jiangqiang.qianyi.common.log.annotations.Log)
            """)
    public void controllerLogMethods() {
        // 这个方法不需要实现
    }

    @Around("controllerLogMethods()")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        //方法签名
        Signature signature = joinPoint.getSignature();
        Method method = ((MethodSignature) signature).getMethod();
        Log annotation = method.getAnnotation(Log.class);
        if (annotation == null) {
            //注解不存在，不记录日志
            return joinPoint.proceed();
        }
        SysLogSaveRequest sysLog = new SysLogSaveRequest();
        sysLog.setRequestTime(LocalDateTime.now());
        String requestUrl = request.getRequestURI();
        String contextPath = request.getContextPath();
        if (StrUtil.isNotBlank(contextPath)) {
            requestUrl = requestUrl.substring(contextPath.length());
        }
        sysLog.setRequestUrl(requestUrl);
        sysLog.setLogName(annotation.value() == null ? "" : annotation.value());
        sysLog.setRequestMethod(request.getMethod().toUpperCase());
        sysLog.setContentType(request.getContentType());
        String requestMethod = sysLog.getRequestMethod();
        String contentType = sysLog.getContentType();
        if (HttpMethod.POST.name().equals(requestMethod) || HttpMethod.PUT.equals(requestMethod) || HttpMethod.PATCH.equals(requestMethod)) {
            if (MediaType.APPLICATION_JSON_VALUE.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                //只处理json请求
                Parameter[] parameters = method.getParameters();
                int i = 0;
                Object[] args = joinPoint.getArgs();
                for (Parameter parameter : parameters) {
                    RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
                    if (requestBody != null) {
                        sysLog.setRequestBody(JsonUtil.toJsonStr(args[i]));
                        break;
                    }
                    i++;
                }
            }
        }
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (CollUtil.isNotEmpty(parameterMap)) {
            Map<String, List<String>> requestParam = parameterMap.entrySet().stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, entry -> List.of(entry.getValue())));
            sysLog.setRequestParam(requestParam);
        } else {
            sysLog.setRequestParam(new HashMap<>());
        }
        Map<String, List<String>> headerMap = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        CollUtil.forEach(headerNames, (index, headerName) -> {
            headerMap.putIfAbsent(headerName, ListUtil.of(request.getHeaders(headerName)));
        });
        sysLog.setRequestHeader(headerMap);
        sysLog.setLoginType(SessionHolder.currentUser().getPlatform());
        sysLog.setToken(request.getHeader(authProperties.getTokenKey()));
        sysLog.setClassName(method.getDeclaringClass().getName());
        sysLog.setMethodName(method.getName());
        sysLog.setUserId(SessionHolder.currentUser().getUserId());
        sysLog.setAuthId(SessionHolder.currentUser().getAuthId());
        sysLog.setRequestIp(ServletUtil.getClientIP(request));
        sysLog.setLogType(annotation.type() == null ? SysLogType.OTHER : annotation.type());
        sysLog.setReferer(request.getHeader(HeaderName.REFERER.getValue()));
        sysLog.setUserAgent(request.getHeader(HeaderName.USER_AGENT.getValue()));
        try {
            Object returnValue = joinPoint.proceed();
            if (returnValue instanceof Result<?> result) {
                sysLog.setResponseCode(result.getCode());
                sysLog.setResponseMessage(result.getMessage());
                sysLog.setResponseData(JsonUtil.toJsonStr(result.getData()));
            }
            sysLog.setHttpCode(200);
            return returnValue;
        } catch (Throwable e) {
            sysLog.setExceptionName(e.getClass().getName());
            sysLog.setExceptionMessage(e.getMessage());
            if (e instanceof BaseException baseException) {
                Result<Object> error = Result.error(baseException);
                sysLog.setHttpCode(baseException.getStatusCode().value());
                sysLog.setResponseCode(error.getCode());
                sysLog.setResponseMessage(error.getMessage());
            } else if (e instanceof ErrorResponseException errorResponseException) {
                HttpStatusCode statusCode = errorResponseException.getStatusCode();
                sysLog.setHttpCode(statusCode.value());
                sysLog.setResponseMessage(errorResponseException.getMessage());
                if (statusCode.is4xxClientError()) {
                    if (statusCode.isSameCodeAs(Error400Status.BAD_REQUEST.getHttpStatus())) {
                        sysLog.setResponseCode(Error400Status.BAD_REQUEST.getCode());
                    } else if (statusCode.isSameCodeAs(Error401Status.UNAUTHORIZED.getHttpStatus())) {
                        sysLog.setResponseCode(Error401Status.UNAUTHORIZED.getCode());
                    } else if (statusCode.isSameCodeAs(Error403Status.FORBIDDEN.getHttpStatus())) {
                        sysLog.setResponseCode(Error403Status.FORBIDDEN.getCode());
                    } else if (statusCode.isSameCodeAs(Error404Status.NOT_FOUND.getHttpStatus())) {
                        sysLog.setResponseCode(Error404Status.NOT_FOUND.getCode());
                    } else {
                        sysLog.setResponseCode(Error400Status.BAD_REQUEST.getCode());
                    }
                } else {
                    sysLog.setResponseCode(Error500Status.INTERNAL_SERVER_ERROR.getCode());
                }
            } else {
                sysLog.setHttpCode(Error500Status.INTERNAL_SERVER_ERROR.getRawStatusCode());
                sysLog.setResponseCode(Error500Status.INTERNAL_SERVER_ERROR.getCode());
                sysLog.setResponseMessage(Error500Status.INTERNAL_SERVER_ERROR.getMessage());
            }
            throw e;
        } finally {
            LocalDateTime after = LocalDateTime.now();
            sysLog.setResponseTime(after);
            sysLog.setProcessingTime((int) TimeUtil.between(Objects.requireNonNull(sysLog.getRequestTime()), after, ChronoUnit.MILLIS));
            logPersistenceService.saveLog(sysLog);
        }
    }
}
